/*
* Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { RosProto } = require("../ros_msg_utils/RosProto");
const { WsClient2 } = require("../../engine/ws/WsClient2");

var ROS_RAW_TYPES = ["bool", "byte", "char", "duration", "float32", "float64", "int16", "int32", "int64", "int8", "string", "time", "uint16", "uint32", "uint64", "uint8",]
var ROS_RAW_VALUES = {
    bool: true,
    byte: 0,
    char: '"c"',
    duration: "{ secs: 0, nsecs: 0 }",
    float32: 0,
    float64: 0,
    int16: 0,
    int32: 0,
    int64: 0,
    int8: 0,
    string: '"s"',
    time: "{ secs: 0, nsecs: 0 }",
    uint16: 0,
    uint32: 0,
    uint64: 0,
    uint8: 0,
};

function get_value_by_type(pt, d) {
    if (d.isArray) {
        switch (d.type) {
            case "bool":
                return pt.get_bool_array(d.arraySize);
            case "byte":
                return pt.get_byte_array(d.arraySize);
            case "char":
                return pt.get_char_array(d.arraySize);
            case "duration":
                return pt.get_duration_array(d.arraySize);
            case "float32":
                return pt.get_float32_array(d.arraySize);
            case "float64":
                return pt.get_float64_array(d.arraySize);
            case "int16":
                return pt.get_int16_array(d.arraySize);
            case "int32":
                return pt.get_int32_array(d.arraySize);
            case "int64":
                return pt.get_int64_array(d.arraySize);
            case "int8":
                return pt.get_int8_array(d.arraySize);
            case "string":
                return pt.get_string_array(d.arraySize);
            case "time":
                return pt.get_time_array(d.arraySize);
            case "uint16":
                return pt.get_uint16_array(d.arraySize);
            case "uint32":
                return pt.get_uint32_array(d.arraySize);
            case "uint64":
                return pt.get_uint64_array(d.arraySize);
            case "uint8":
                return pt.get_uint8_array(d.arraySize);
            default:
                {
                    let msg_type = d.node + "/" + d.type;
                    let objMsg = EnvManager.getInstance().getMsgObj(msg_type);
                    let size = pt.get_uint32();
                    let arr = [];
                    for (let i = 0; i < size; i++) {
                        arr.push(objMsg.get_msg(pt, msg_type));
                    }
                    return arr;
                }
        }
    }
    else {
        switch (d.type) {
            case "bool":
                return pt.get_bool();
            case "byte":
                return pt.get_byte();
            case "char":
                return pt.get_char();
            case "duration":
                return pt.get_duration();
            case "float32":
                return pt.get_float32();
            case "float64":
                return pt.get_float64();
            case "int16":
                return pt.get_int16();
            case "int32":
                return pt.get_int32();
            case "int64":
                return pt.get_int64();
            case "int8":
                return pt.get_int8();
            case "string":
                return pt.get_string();
            case "time":
                return pt.get_time();
            case "uint16":
                return pt.get_uint16();
            case "uint32":
                return pt.get_uint32();
            case "uint64":
                return pt.get_uint64();
            case "uint8":
                return pt.get_uint8();
            default:
                {
                    let msg_type = d.node + "/" + d.type;
                    return EnvManager.getInstance().getMsgObj(msg_type).get_msg(pt, msg_type);
                }
        }
    }
}

function put_value_by_type(pt, d, value) {
    if (d.isArray) {
        switch (d.type) {
            case "bool":
                pt.put_bool_array(value);
                break;
            case "byte":
                pt.put_byte_array(value);
                break;
            case "char":
                pt.put_char_array(value);
                break;
            case "duration":
                pt.put_duration_array(value);
                break;
            case "float32":
                pt.put_float32_array(value);
                break;
            case "float64":
                pt.put_float64_array(value);
                break;
            case "int16":
                pt.put_int16_array(value);
                break;
            case "int32":
                pt.put_int32_array(value);
                break;
            case "int64":
                pt.put_int64_array(value);
                break;
            case "int8":
                pt.put_int8_array(value);
                break;
            case "string":
                pt.put_string_array(value);
                break;
            case "time":
                pt.put_time_array(value);
                break;
            case "uint16":
                pt.put_uint16_array(value);
                break;
            case "uint32":
                pt.put_uint32_array(value);
                break;
            case "uint64":
                pt.put_uint64_array(value);
                break;
            case "uint8":
                pt.put_uint8_array(value);
                break;
            default:
                {
                    let msg_type = d.node + "/" + d.type;
                    let objMsg = EnvManager.getInstance().getMsgObj(msg_type);
                    pt.put_uint32(value.length);
                    for (let i = 0; i < value.length; i++) {
                        objMsg.put_msg(pt, value[i]);
                    }
                }
        }
    }
    else {
        switch (d.type) {
            case "bool":
                pt.put_bool(value);
                break;
            case "byte":
                pt.put_byte(value);
                break;
            case "char":
                pt.put_char(value);
                break;
            case "duration":
                pt.put_duration(value);
                break;
            case "float32":
                pt.put_float32(value);
                break;
            case "float64":
                pt.put_float64(value);
                break;
            case "int16":
                pt.put_int16(value);
                break;
            case "int32":
                pt.put_int32(value);
                break;
            case "int64":
                pt.put_int64(value);
                break;
            case "int8":
                pt.put_int8(value);
                break;
            case "string":
                pt.put_string(value);
                break;
            case "time":
                pt.put_time(value);
                break;
            case "uint16":
                pt.put_uint16(value);
                break;
            case "uint32":
                pt.put_uint32(value);
                break;
            case "uint64":
                pt.put_uint64(value);
                break;
            case "uint8":
                pt.put_uint8(value);
                break;
            default:
                {
                    let msg_type = d.node + "/" + d.type;
                    let objMsg = EnvManager.getInstance().getMsgObj(msg_type);
                    objMsg.put_msg(pt, value);
                }
        }
    }
}

class DefMsg {
    constructor() {
    }
}
class BaseMsg {
    constructor(name) {
        this.name = name;
        this.defs = [];
    }
    mock_js_msg(deep = "    ") {//创建js对象文本，供开发者修改
        let js_msg = "{\n";
        deep += "    ";
        for (let d of this.defs) {
            if (d.isConst) {
                js_msg += deep + d.name + ": " + d.constValue + ",\n";
            } else {
                if (d.node == "raw") {
                    js_msg += deep + d.name + ": ";
                    if (d.isArray) js_msg += "[";
                    js_msg += ROS_RAW_VALUES[d.type];
                    if (d.isArray) js_msg += "]";
                    js_msg += ", // " + d.type + "\n";
                } else {
                    let msg_type = d.node + "/" + d.type;
                    let msg_obj = EnvManager.getInstance().getMsgObj(msg_type);
                    if (msg_obj) {
                        if (d.isArray) {
                            js_msg += deep + d.name + ": [" + msg_obj.mock_js_msg(deep) + "], // " + d.node + "/" + d.type + "\n";
                        } else {
                            js_msg += deep + d.name + ": " + msg_obj.mock_js_msg(deep) + ", // " + d.node + "/" + d.type + "\n";
                        }
                    }
                }
            }
        }
        js_msg += deep.substring(4) + "}";
        return js_msg;
    }
    dump_struct(prefix = "", deep = 0) { // 消息转文本结构，展示给开发者
        let ss_ret = "";
        let ss_head = "";
        for (let i = 0; i < deep; i++) {
            ss_head += "    ";
        }
        for (let d of this.defs) {
            ss_ret += ss_head + prefix + d.name + " : " + (d.node == "raw" ? "" : (d.node + "/")) + d.type;
            if (d.isArray) {
                ss_ret += "[";
                if (d.arraySize > 0) {
                    ss_ret += d.arraySize;
                }
                ss_ret += "]";
            }
            if (d.isConst) {
                ss_ret += " = " + d.constValue;
            }
            ss_ret += "\n";
            if (d.type != "raw") {
                let msg_type = d.node + "/" + d.type;
                let msg_obj = EnvManager.getInstance().getMsgObj(msg_type);
                if (msg_obj) {
                    ss_ret += msg_obj.dump_struct(prefix + d.name + ".", deep + 1);
                }
            }
        }
        return ss_ret;
    }
    put_msg(pt, msg) {
        for (let d of this.defs) {
            if (d.isConst) {
                //todo 
            } else {
                put_value_by_type(pt, d, msg[d.name]);
            }
        }
    }
    get_msg(pt) {
        let msg = {}
        for (let d of this.defs) {
            if (d.isConst) {
                //todo 
            } else {
                msg[d.name] = get_value_by_type(pt, d)
            }
        }
        return msg;
    }
}
class NodeMsg {
    constructor(name) {
        this.name = name;
        this.msgs = {};
    }
    findMsg(name) {
        return this.msgs[name];
    }
    addMsg(name) {
        this.msgs[name] = new BaseMsg(name);
    }
    updateMsg(name, msg) {
        this.msgs[name].defs = msg;
    }
}

class EnvManager {
    constructor() {
        this.nodes = {};

        WsClient2.registProcess("query_msg_define_ret", (pt_ret) => {
            let msg = pt_ret.get_string().split("/");
            let success = pt_ret.get_string();
            if (success == "ok") {
                let node = msg[0];
                let name = msg[1];
                let paramCount = pt_ret.get_uint32();
                if (node in this.nodes) {
                    let def = []
                    for (let i = 0; i < paramCount; i++) {
                        let ret = new DefMsg();
                        ret.node = pt_ret.get_string();
                        ret.type = pt_ret.get_string();
                        ret.name = pt_ret.get_string();
                        ret.isConst = pt_ret.get_bool();
                        ret.constValue = pt_ret.get_string();
                        ret.isArray = pt_ret.get_bool();
                        ret.arraySize = pt_ret.get_uint32();
                        def.push(ret)
                        if (ret.node != "raw") {//如果type不属于基础类型，则需要查询其定义
                            if (this.getMsgObj(ret.node + "/" + ret.type) == null) {
                                this.addMsgObj(ret.node + "/" + ret.type);
                            }
                        }
                    }
                    this.nodes[node].updateMsg(name, def);
                    // console.log("msg", msg, "ok")
                }
            }
        })
    }
    static getInstance() {
        if (!EnvManager.instance) {
            EnvManager.instance = new EnvManager();
        }
        return EnvManager.instance;
    }
    addMsgObj(msgType) {//比如sensor_msgs/Image，分成两个参数
        let t = msgType.split("/")
        let node = t[0];
        let name = t[1];
        if (!this.nodes[node]) {
            this.nodes[node] = new NodeMsg(node);
        }
        if (!this.nodes[node].findMsg(name)) {
            this.nodes[node].addMsg(name);
            RosProto.queryMsgDefine(msgType);
        }
    }
    getMsgObj(msgType) {
        if (!msgType) return null;
        let t = msgType.split("/")
        let node = t[0];
        let name = t[1];
        if (!(node in this.nodes)) {
            this.addMsgObj(msgType);
            return null;
        }
        if (!(name in this.nodes[node].msgs)) {
            this.addMsgObj(msgType);
            return null;
        }
        return this.nodes[node].msgs[name];
    }
}

module.exports = {
    EnvManager
};